<?php

require_once(dirname(__FILE__)."/../OAuth/OAuth.php");

class MySpaceException extends Exception {
  const TOKEN_REQUIRED = 1; 
  const REMOTE_ERROR   = 2; 
  const REQUEST_FAILED = 3; 
  const CONNECT_FAILED = 4; 

  public $response; 

  function __construct($msg, $code, $response=null) {
    parent::__construct($msg, $code);
    $this->response = $response;
  }
}

class MySpace {

  public static $MS_API_ROOT = "http://api.myspace.com";
   
  public static $MS_DUMP_REQUESTS = false; // set to a pathname to dump out http requests to a log. For example, "./ms.log"

  // OAuth URLs
  function requestTokenURL() { return self::$MS_API_ROOT.'/request_token'; }
  function authorizeURL() { return self::$MS_API_ROOT.'/authorize'; }  
  function accessTokenURL() { return self::$MS_API_ROOT.'/access_token'; }
  
  // API URLs
  function userInfoURL() { return self::$MS_API_ROOT.'/v1/user.json'; }
  function profileURL($userid) { return self::$MS_API_ROOT.'/v1/users/'.$userid.'/profile.json'; }
  function friendsURL($userid) { return self::$MS_API_ROOT.'/v1/users/'.$userid.'/friends.json'; }
    
  function __construct($consumerKey,
		       $consumerSecret, 
		       $oAuthToken = null, 
		       $oAuthTokenSecret = null)  {
    $this->sha1_method = new OAuthSignatureMethod_HMAC_SHA1();
    $this->consumer = new OAuthConsumer($consumerKey, $consumerSecret, NULL);
    if (!empty($oAuthToken)) {    
    	$this->token = new OAuthConsumer($oAuthToken, $oAuthTokenSecret);
    } else {
      $this->token = NULL;
    }
  }

    /**
   * Get a request token for authenticating your application with FE.
   *
   * @returns a key/value pair array containing: oauth_token and
   * oauth_token_secret.
   */
  public function getRequestToken() {
    $r = $this->oAuthRequest($this->requestTokenURL());
    $token = $this->oAuthParseResponse($r);
    $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']); // use this token from now on
    if (self::$MS_DUMP_REQUESTS) self::dump("Now the user is redirected to ".$this->getAuthorizeURL($token['oauth_token'])."\nOnce the user returns, via the callback URL for web authentication or manually for desktop authentication, we can get their access token and secret by calling /oauth/access_token.\n\n");
    return $token;
  }
  public function request_token() { return $this->getRequestToken(); }

  /**
   * Get the URL to redirect to to authorize the user and validate a
   * request token.
   *
   * @returns a string containing the URL to redirect to.
   */
  public function getAuthorizeURL($token) {
    // $token can be a string, or an array in the format returned by getRequestToken().
    if (is_array($token)) $token = $token['oauth_token'];
    return $this->authorizeURL() . '?oauth_token=' . $token . '&oauth_callback=http://' . $_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME'] . '?f=callback';
  }
  public function authorize($token) { return $this->getAuthorizeURL($token); }
  
   /**
   * Exchange the request token and secret for an access token and
   * secret, to sign API calls.
   *
   *
   * @returns array("oauth_token" => the access token,
   *                "oauth_token_secret" => the access secret)
   */
  public function getAccessToken($token=NULL) {
    $this->requireToken();
    $r = $this->oAuthRequest($this->accessTokenURL());
    $token = $this->oAuthParseResponse($r);
    $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']); // use this token from now on
    return $this->token;
  }
  public function access_token() { return $this->getAccessToken(); }

  public function call($url, $params=array(), $request_method=NULL) {
    $this->requireToken();
    $r = $this->oAuthRequest($url, $params, $request_method);
    return $this->parseJSON($r);
  }

  public function userInfo() {
    $r = $this->call($this->userInfoURL()); 
    return $r;    
  }
  
  public function profile($userid) {
    $r = $this->call($this->profileURL($userid)); 
    return $r;
  }

  public function friends($userid) {
    $r = $this->call($this->friendsURL($userid)); 
    return $r;
  }
  
  protected function parseJSON($json) {
    $r = json_decode($json);
    if (empty($r)) throw new MySpaceException("Empty JSON response", MySpaceException::REQUEST_FAILED);
    if (isset($r->rsp) && $r->rsp->stat != 'ok') {
      throw new MySpaceException($r->rsp->code.": ".$r->rsp->message, MySpaceException::REMOTE_ERROR, $r->rsp);
    }
    return $r;
  }

  protected function requireToken() {
    if (!isset($this->token)) {
      throw new MySpaceException("This function requires an OAuth token", MySpaceException::TOKEN_REQUIRED);
    }
  }
  
  // Parse a URL-encoded OAuth response
  protected function oAuthParseResponse($responseString) {
    $r = array();
    foreach (explode('&', $responseString) as $param) {
      $pair = explode('=', $param, 2);
      if (count($pair) != 2) continue;
      $r[urldecode($pair[0])] = urldecode($pair[1]);
    }  
    return $r;
  }

  // Format and sign an OAuth / API request
  function oAuthRequest($url, $args=array(), $method=NULL) {
    if (empty($method)) $method = empty($args) ? "GET" : "POST";
    $req = OAuthRequest::from_consumer_and_token($this->consumer, $this->token, $method, $url, $args);
    $req->sign_request($this->sha1_method, $this->consumer, $this->token);
    if (self::$MS_DUMP_REQUESTS) {
      $k = $this->consumer->secret . "&";
      if ($this->token) $k .= $this->token->secret;
      self::dump("---\n\nOAUTH REQUEST TO $url");
      if (!empty($args)) self::dump(" WITH PARAMS ".json_encode($args));
      self::dump("\n\nBase string: ".$req->base_string."\nSignature string: $k\n");
    }
    switch ($method) {
    case 'GET': return $this->http($req->to_url());
    case 'POST': return $this->http($req->get_normalized_http_url(), $req->to_postdata());
    }
  }

  // Make an HTTP request, throwing an exception if we get anything other than a 200 response
  public function http($url, $postData=null) {
    if (self::$MS_DUMP_REQUESTS) {
      self::dump("Final URL: $url\n\n");
      $url_bits = parse_url($url);
      if (isset($postData)) {
	self::dump("POST ".$url_bits['path']." HTTP/1.0\nHost: ".$url_bits['host']."\nContent-Type: application/x-www-urlencoded\nContent-Length: ".strlen($postData)."\n\n$postData\n");
      } else {
	$get_url = $url_bits['path'];
	if ($url_bits['query']) $get_url .= '?' . $url_bits['query'];
	self::dump("GET $get_url HTTP/1.0\nHost: ".$url_bits['host']."\n\n");
      }
    }
    $ch = curl_init();
    if (defined("CURL_CA_BUNDLE_PATH")) curl_setopt($ch, CURLOPT_CAINFO, CURL_CA_BUNDLE_PATH);
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
    curl_setopt($ch, CURLOPT_TIMEOUT, 30);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    if (isset($postData)) {
      curl_setopt($ch, CURLOPT_POST, 1);
      curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
    }
    $response = curl_exec($ch);
    $status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $ct = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
    if ($ct) $ct = preg_replace("/;.*/", "", $ct); // strip off charset
    if (!$status) throw new MySpaceException("Connection to $url failed", MySpaceException::CONNECT_FAILED);
    if ($status != 200) {
      if ($ct == "application/json") {
	$r = json_decode($response);
	if ($r && isset($r->rsp) && $r->rsp->stat != 'ok') {
	  throw new MySpaceException($r->rsp->code.": ".$r->rsp->message, MySpaceException::REMOTE_ERROR, $r->rsp);
	}
      }
      throw new MySpaceException("Request to $url failed: HTTP error $status ($response)", MySpaceException::REQUEST_FAILED);
    }
    if (self::$MS_DUMP_REQUESTS) {
      self::dump("HTTP/1.0 $status OK\n");
      if ($ct) self::dump("Content-Type: $ct\n");
      $cl = curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD);
      if ($cl) self::dump("Content-Length: $cl\n");
      self::dump("\n$response\n\n");
    }
    curl_close ($ch);
   
    return $response;
  }

  private function dump($text) {
    if (!self::$MS_DUMP_REQUESTS) throw new Exception('MySpace::$MS_DUMP_REQUESTS must be set to enable request trace dumping');
    file_put_contents(self::$MS_DUMP_REQUESTS, $text, FILE_APPEND);
  }

}

